﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.ServiceModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using Bookvar.ApplicationModel;
using Bookvar.Collaboration.ClientSide;
using Bookvar.Collaboration.ServerSide;
using Bookvar.ObjectModel;
using Bookvar.ObjectModel.Actions.Collaboration;
using Bookvar.ObjectModel.Collections;
using Bookvar.ServiceModel;
using System.Diagnostics;

namespace Bookvar.Collaboration
{
    /// <summary>
    /// This class manages the collaboration. It is a facade for all collaboration logic.
    /// </summary>
    public sealed class CollaborationManager :
        ICollaborationManager,
        ICollaborationEventsPublisher,
        IExecutionContext,
        IDisposable
    {
        internal static ServiceHost myServiceHost;

        private event EventHandler<CollaborationUserMessageEventArgs> userSaid;
        private event EventHandler<CollaborationUserMessageEventArgs> userWhispered;
        private event EventHandler<CollaborationUserEventArgs> userJoined;
        private event EventHandler<CollaborationUserEventArgs> userLeft;
        private event EventHandler collaborationStopped;
        private event EventHandler serverStarted;
        private event EventHandler connectionToServerLost;
        private event EventHandler serverStopped;
        private event EventHandler clientConnected;

        private CollaborationProxy clientProxy;
        private CollaborationUserCollection clientSideCollaborationUsers;
        private CollaborationMode collaborationMode;
        private CollaborationUser currentClientUser;
        private ExecutionMode executionMode;
        private MindMap mindmap;
        private ICollaborationUsersManager serverUsersManager;

        public CollaborationManager()
        {
            collaborationMode = CollaborationMode.Normal;
            executionMode = ExecutionMode.None;
        }

        private static ICollaborationUsersManager UsersManager
        {
            get { return BookvarApplication.Current.ServiceLayer.GetService<ICollaborationUsersManager>(); }
        }

        #region ICollaborationManager Members

        /// <summary>
        /// Gets the collaboration mode.
        /// </summary>
        /// <value>The collaboration mode.</value>
        CollaborationMode ICollaborationManager.CollaborationMode
        {
            get { return collaborationMode; }
        }

        void ICollaborationManager.Say(string message)
        {
            switch (collaborationMode)
            {
                case CollaborationMode.Normal:
                    break;

                case CollaborationMode.Server:
                    ICollaborationUsersManager usersManager =
                        BookvarApplication.Current.ServiceLayer.GetService<ICollaborationUsersManager>();

                    if (usersManager != null)
                    {
                        usersManager.InformProcessAction(new SayAction(usersManager.Host, message));
                    }
                    break;

                case CollaborationMode.Client:
                    if (clientProxy.State == CommunicationState.Opened)
                    {
                        try
                        {
                            clientProxy.ProcessAction(new SayAction(CurrentCollaborationUser, message));
                        }
                        catch(TimeoutException)
                        {
                            DoStopCollaboration(true);
                            OnConnectionToServerLost();
                        }
                    }
                    else
                    {
                        StopClient();
                    }
                    break;

                default:
                    Debug.Fail( "Unexpected CollaborationMode" );
                    break;
            }
        }

        void ICollaborationManager.Whisper(Guid userKey, string message)
        {
            throw new NotImplementedException( "The method or operation is not implemented." );
        }

        /// <summary>
        /// Gets the current collaboration user.
        /// </summary>
        /// <value>The current collaboration user.</value>
        public CollaborationUser CurrentCollaborationUser
        {
            get
            {
                switch (collaborationMode)
                {
                    case CollaborationMode.Normal:
                        return null;

                    case CollaborationMode.Server:
                        ICollaborationUsersManager manager = UsersManager;
                        if (manager != null)
                        {
                            return manager.Host;
                        }
                        return null;

                    case CollaborationMode.Client:
                        return currentClientUser;

                    default:
                        return null;
                }
            }
        }

        MindMap ICollaborationManager.GetMindmap()
        {
            if (collaborationMode == CollaborationMode.Client && clientProxy != null)
            {
                try
                {
                    MindMap map = clientProxy.GetMindmap();
                    mindmap = map;
                    map.ActionHappened += OnClientMindMapDoActionEvent;
                    return map;
                }
                catch (TimeoutException)
                {
                    DoStopCollaboration(true);
                    OnConnectionToServerLost();
                    return null;
                }
            }
            else
            {
                throw new InvalidOperationException("Client not connected / or already disconnected.");
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public void SendAction(ActionBase action)
        {
            if (collaborationMode == CollaborationMode.Client && clientProxy != null)
            {
                try
                {
                    clientProxy.ProcessAction(action);
                }
                catch (TimeoutException)
                {
                    DoStopCollaboration(true);
                    OnConnectionToServerLost();
                }
                catch (Exception ex)
                {
                    Logger.Log(ex.ToString());
                    DoStopCollaboration();
                }
            }
        }

        public void StartServer(string hostName, MindMap map, Uri hostAddress)
        {
            ValidateCollaborationNotStarted();

            if (hostName == null)
            {
                throw new ArgumentNullException("hostName");
            }

            if (map == null)
            {
                throw new ArgumentNullException("map");
            }

            // TODO: Think of smarter way. Maybe refactor the CollaborationService class to 
            // get the CollaborationManager from the services.
            CollaborationService.MindMap = map;
            mindmap = map;

            serverUsersManager = new CollaborationUsersManager(hostName);
            serverUsersManager.UserJoined += serverUsersManager_UserJoined;
            serverUsersManager.UserLeft += serverUsersManager_UserLeft;
            BookvarApplication.Current.ServiceLayer.AddService(serverUsersManager);

            CollaborationService.SetExecutionContext(this);
            //context.MainTopic.ActionHappened += OnMindMapDoActionEvent;
            map.ActionHappened += OnServerMindMapDoActionEvent;

            myServiceHost = new ServiceHost(typeof(CollaborationService), hostAddress);

            myServiceHost.Open();

            collaborationMode = CollaborationMode.Server;

            OnServerStarted();
            
            Logger.Log("Server started.");
        }

        public void StartServer(string hostName, MindMap map)
        {
            Uri baseAddress = new Uri(ConfigurationManager.AppSettings["addr"]);
            StartServer(hostName, map, baseAddress);
        }

        private void serverUsersManager_UserJoined(object sender, CollaborationUserEventArgs e)
        {
            OnUserJoined(e);
        }

        private void serverUsersManager_UserLeft(object sender, CollaborationUserEventArgs e)
        {
            OnUserLeft(e);
        }

        /// <summary>
        /// Connects to a collaboration server as a client.
        /// </summary>
        /// <exception cref="InvalidOperationException">If the collaboration server or client is already started.</exception>
        void ICollaborationManager.ConnectToServer(string uri)
        {
            ValidateCollaborationNotStarted();
        }

        IAsyncResult ICollaborationManager.BeginConnectToServer(string serverAddress,
                                                                string clientName,
                                                                AsyncCallback callback)
        {
            collaborationMode = CollaborationMode.Client;

            CollaborationServiceCallback csc = new CollaborationServiceCallback(this);
            InstanceContext instanceContext = new InstanceContext(csc);
            clientProxy = new CollaborationProxy(instanceContext, serverAddress);
            return clientProxy.BeginJoin(clientName, callback, null);
        }

        CollaborationUser[] ICollaborationManager.EndConnectToServer(IAsyncResult asyncResult)
        {
            if (collaborationMode != CollaborationMode.Client)
            {
                throw new InvalidOperationException("Collaboration client is not started.");
            }

            UserJoinResponse response = clientProxy.EndJoin(asyncResult);
            currentClientUser = response.CurrentUser;

            clientSideCollaborationUsers = new CollaborationUserCollection( response.Users );

            return response.Users;
        }

        /// <summary>
        /// Stops the collaboration. 
        /// </summary>
        /// If the collaboraiton server is started it is stopped. If the client is started - it is disconnected.
        /// <exception cref="InvalidOperationException">If the collaboration server or client is not started or is already closed.</exception>
        void ICollaborationManager.StopCollaboration()
        {
            try
            {
                DoStopCollaboration();
            }
            catch (Exception ex)
            {
                Logger.Log(ex.ToString());
                throw new CollaborationException("Collaboration client error while disconnecting", ex);
            }
        }

        /// <summary>
        /// Gets the collaboration users. 
        /// </summary>
        /// <value>The collaboration users.</value>
        public CollaborationUserCollection CollaborationUsers
        {
            get
            {
                if (collaborationMode == CollaborationMode.Server)
                {
                    ICollaborationUsersManager usersManager = UsersManager;
                    if (usersManager != null)
                    {
                        return usersManager.CollaborationUsers;
                    }
                }

                if (collaborationMode == CollaborationMode.Client)
                {
                    return clientSideCollaborationUsers;
                }

                Debug.Assert(false);

                return new CollaborationUserCollection();
            }
        }

        event EventHandler ICollaborationManager.ServerStarted
        {
            add { serverStarted += value; }
            remove { serverStarted -= value; }
        }

        event EventHandler ICollaborationManager.ClientConnected
        {
            add {
                clientConnected += value; 
            }
            remove
            {
                clientConnected -= value; 
            }
        }

        event EventHandler ICollaborationManager.CollaborationStopped
        {
            add { collaborationStopped += value; }
            remove { collaborationStopped -= value; }
        }

        event EventHandler<CollaborationUserMessageEventArgs> ICollaborationManager.UserSaid
        {
            add { userSaid += value; }
            remove { userSaid -= value; }
        }

        event EventHandler<CollaborationUserMessageEventArgs> ICollaborationManager.UserWhispered
        {
            add { userWhispered += value; }
            remove { userWhispered -= value; }
        }

        event EventHandler<CollaborationUserEventArgs> ICollaborationManager.UserJoined
        {
            add { userJoined += value; }
            remove { userJoined -= value; }
        }

        event EventHandler<CollaborationUserEventArgs> ICollaborationManager.UserLeft
        {
            add { userLeft += value; }
            remove { userLeft -= value; }
        }

        event EventHandler ICollaborationManager.ConnectionToServerLost
        {
            add { connectionToServerLost += value; }
            remove { connectionToServerLost -= value; }
        }

        event EventHandler ICollaborationManager.ServerStopped
        {
            add { serverStopped += value; }
            remove { serverStopped -= value; }
        }

        #endregion

        #region ICollaborationEventsPublisher

        void ICollaborationEventsPublisher.PublishUserSaid(CollaborationUser user, string message)
        {
            CollaborationUserMessageEventArgs eventArgs = new CollaborationUserMessageEventArgs(user, message);
            OnUserSaid(eventArgs);
        }

        void ICollaborationEventsPublisher.PublishUserWhispered(CollaborationUser user, string message)
        {
            CollaborationUserMessageEventArgs eventArgs = new CollaborationUserMessageEventArgs(user, message);
            OnUserWhispered(eventArgs);
        }

        void ICollaborationEventsPublisher.PublishUserJoined(CollaborationUser user)
        {
            clientSideCollaborationUsers.Add(user);

            CollaborationUserEventArgs eventArgs = new CollaborationUserEventArgs(user);
            OnUserJoined(eventArgs);
        }

        void ICollaborationEventsPublisher.PublishUserLeft(CollaborationUser user)
        {
            // TODO check if this is working!
            clientSideCollaborationUsers.Remove(user);

            CollaborationUserEventArgs eventArgs = new CollaborationUserEventArgs(user);
            OnUserLeft(eventArgs);
        }

        void ICollaborationEventsPublisher.PublishServerClosed(CollaborationUser host)
        {
            DoStopCollaboration();
            OnServerStopped();
        }

        #endregion

        #region IExecutionContext members

        CollaborationMode IExecutionContext.CollaborationMode
        {
            get { return collaborationMode; }
        }

        ExecutionMode IExecutionContext.ExecutionMode
        {
            get { return executionMode; }
            set { executionMode = value; }
        }

        MainTopic IExecutionContext.MainTopic
        {
            get { return mindmap.MainTopic; }
        }

        void IExecutionContext.HighlightTopic(Topic topic, Guid userId)
        {
            IAdornerService adornerService =
                BookvarApplication.Current.ServiceLayer.GetService<IAdornerService>();

            CollaborationUser user = GetUserById(userId);
            if (user != null && adornerService != null)
            {
                UIElement element = CreateDefaultCollaborationTopicAdorner(user);
                adornerService.AdornTopic(topic, element, AdornerPosition.TopRight, AdornerPlacement.In);
            }
        }

        public static UIElement CreateDefaultCollaborationTopicAdorner(CollaborationUser user)
        {
            Ellipse circle = new Ellipse();
            circle.Width = 9;
            circle.Height = 9;
            circle.Fill = new SolidColorBrush(user.UserColor);
            circle.Margin = new Thickness(0, 7, 5, 0);
            return circle;
        }

        MindMap IExecutionContext.Mindmap
        {
            get { return mindmap; }
        }

        private CollaborationUser GetUserById(Guid id)
        {
            foreach (CollaborationUser user in CollaborationUsers)
            {
                if (user.ID == id)
                {
                    return user;
                }
            }
            return null;
        }

        #endregion

        private void OnServerMindMapDoActionEvent(object sender, ActionEventArgs e)
        {
            if (executionMode == ExecutionMode.Execution)
            {
                return;
            }

            e.Action.PublisherGuid = UsersManager.Host.ID;
            UsersManager.InformProcessAction(e.Action);
        }

        private void OnClientMindMapDoActionEvent(object sender, ActionEventArgs e)
        {
            if (executionMode == ExecutionMode.Execution)
            {
                return;
            }

            if (collaborationMode != CollaborationMode.Normal)
            {
                // testing
                Logger.Log(("topic1_ActionHappened" + e.Action));

                SendAction(e.Action);
            }
        }

        private void ValidateCollaborationNotStarted()
        {
            if (collaborationMode == CollaborationMode.Server || collaborationMode == CollaborationMode.Client)
            {
                throw new InvalidOperationException("Collaboration already started.");
            }
        }

        private void DoStopCollaboration()
        {
            DoStopCollaboration(false);
        }

        private void DoStopCollaboration(bool forcefully)
        {
            try
            {
                switch (collaborationMode)
                {
                    case CollaborationMode.Normal:
                        break;

                    case CollaborationMode.Server:
                        StopServer();
                        break;

                    case CollaborationMode.Client:
                        StopClient(forcefully);
                        break;

                    default:
                        Debug.Fail("Unrecognized CollaborationMode.");
                        break;
                }
            }
            finally
            {
                CleanUp();
                collaborationMode = CollaborationMode.Normal;
                OnCollaborationStopped();
            }
        }

        private void StopClient()
        {
            StopClient(false);
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void StopClient(bool forcefully)
        {
            try
            {
                if (collaborationMode == CollaborationMode.Client && forcefully == false)
                {
                    clientProxy.Leave();
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex.ToString());
            }
            finally
            {
                if (clientProxy != null)
                {
                    clientProxy.Abort();
                    clientProxy.Close();
                    clientProxy = null;
                }
            }
        }

        private void StopServer()
        {
            if (collaborationMode == CollaborationMode.Server)
            {
                UsersManager.InformServerClosed();
                if (myServiceHost.State != CommunicationState.Closed)
                {
                    myServiceHost.Close();
                }
            }
        }

        private void CleanUp()
        {
            if (serverUsersManager != null)
            {
                serverUsersManager.UserJoined -= serverUsersManager_UserJoined;
                serverUsersManager.UserLeft -= serverUsersManager_UserLeft;
                serverUsersManager = null;
            }

            if (mindmap != null)
            {
                mindmap.ActionHappened -= OnServerMindMapDoActionEvent;
                mindmap.ActionHappened -= OnClientMindMapDoActionEvent;
            }

            BookvarApplication.Current.ServiceLayer.RemoveService<ICollaborationUsersManager>();

            clientSideCollaborationUsers = new CollaborationUserCollection();
            currentClientUser = null;
        }

        private void OnUserSaid(CollaborationUserMessageEventArgs eventArgs)
        {
            EventHandler<CollaborationUserMessageEventArgs> toInvoke = userSaid;

            if (toInvoke != null)
            {
                toInvoke(this, eventArgs);
            }
        }

        private void OnUserWhispered(CollaborationUserMessageEventArgs eventArgs)
        {
            EventHandler<CollaborationUserMessageEventArgs> toInvoke = userWhispered;

            if (toInvoke != null)
            {
                toInvoke(this, eventArgs);
            }
        }

        private void OnUserJoined(CollaborationUserEventArgs eventArgs)
        {
            EventHandler<CollaborationUserEventArgs> toInvoke = userJoined;

            if (toInvoke != null)
            {
                toInvoke(this, eventArgs);
            }
        }

        private void OnUserLeft(CollaborationUserEventArgs eventArgs)
        {
            EventHandler<CollaborationUserEventArgs> toInvoke = userLeft;

            if (toInvoke != null)
            {
                toInvoke(this, eventArgs);
            }
        }

        private void OnServerStarted()
        {
            EventHandler toInvoke = serverStarted;

            if (toInvoke != null)
            {
                toInvoke(this, EventArgs.Empty);
            }
        }

        private void OnCollaborationStopped()
        {
            EventHandler toInvoke = collaborationStopped;

            if (toInvoke != null)
            {
                toInvoke(this, EventArgs.Empty);
            }
        }

        private void OnConnectionToServerLost()
        {
            EventHandler toInvoke = connectionToServerLost;

            if(toInvoke != null)
            {
                toInvoke(this, EventArgs.Empty);
            }
        }

        private void OnServerStopped()
        {
            EventHandler toInvoke = serverStopped;

            if (toInvoke != null)
            {
                toInvoke(this, EventArgs.Empty);
            }
        }

        public void Dispose()
        {
            if ( clientProxy != null )
            {
                ((IDisposable)clientProxy).Dispose();
            }
        }
    }
}